#include    "request.h"
#include    "../utils/logger.h"
#include    "../utils/os.h"
#include    "../utils/utils.h"

#include    <cstring>

#if defined(_WIN32)
#   include <Windows.h>
#else
#   include <netinet/in.h>
#endif

static int QueryIterator(void * p, MHD_ValueKind kind, const char * key, const char * val) {
    lua_State * lua = (lua_State *)p;
    size_t size = strlen(key);

    if (size > 2 && key[size - 2] == '[' && key[size - 1] == ']') {
        std::string name(key, size - 2);
        
        lua_getfield(lua, -1, name.c_str());
        if (!lua_istable(lua, -1)) {
            lua_pop(lua, 1);
            lua_newtable(lua);
            lua_pushstring(lua, val);
            lua_rawseti(lua, -2, 1);
            lua_setfield(lua, -2, name.c_str());
        } else {
            int n = (int)lua_objlen(lua, -1);
            lua_pushstring(lua, val);
            lua_rawseti(lua, -2, n + 1);
            lua_pop(lua, 1);
        }
    } else {
        lua_pushstring(lua, val);
        lua_setfield(lua, -2, key);
    }

    return MHD_YES;
}

static int PostIterator(void *cls, MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) {
    Request::PostData * post = (Request::PostData *)cls;
    if (!post || size == 0) return MHD_YES;

    if (filename) {
        post->params[key] = filename;

        if (post->file_name != filename) {
            if (post->file) {
                fflush(post->file);
                fclose(post->file);
                post->file == NULL;
            }
            
            std::string path = tmpnam(nullptr);
            post->file_name = filename;
            post->file = fopen(path.c_str(), "wb+");
            post->uploaded[filename] = path;

            if (!post->file) {
                Logger::Instance().Error("Failed to create file %s for store : %s", path.c_str(), filename);
                return MHD_NO;
            }
        }

        if (fwrite(data, 1, size, post->file) < size) {
            Logger::Instance().Error("Failed to write data to store %s at offset : %llu, size : %d", filename, off, size);
            return MHD_NO;
        }
    } else {
        size_t key_len = strlen(key);
        if (key_len > 2 && key[key_len - 2] == '[' && key[key_len - 1] == ']') {
            std::string new_key(key, key_len - 2);
            if (post->arrays.find(new_key) == post->arrays.end()) post->arrays[new_key] = std::vector<std::string>();
            post->arrays[new_key].push_back(data);
        } else {
            if (post->params.find(key) == post->params.end()) post->params[key] = "";
            post->params[key].append(data, size);
        }
    }

    return MHD_YES;
}

bool Request::Prepare(MHD_Connection * conn, lua_State * lua, double & start_time, const std::string & url, const std::string & method, const char * upload_data, size_t * upload_size, void ** conn_data) {
    PostData * post_data = (PostData *)(*conn_data);

    if (method == "POST") {
        if (!post_data) {
            post_data = new PostData;

            post_data->pp = MHD_create_post_processor(conn, 65536, &PostIterator, post_data);
            post_data->start_time = start_time;
            post_data->file = nullptr;

            (*conn_data) = post_data;
            return false;
        }

        if (*upload_size != 0) {
            MHD_post_process(post_data->pp, upload_data, *upload_size);
            *upload_size = 0;
            return false;
        }

        start_time = post_data->start_time;
        if (post_data->file) {
            fflush(post_data->file);
            fclose(post_data->file);
            post_data->file = NULL;
        }
    }
    
    const MHD_ConnectionInfo * info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
    uint32_t ip = ((sockaddr_in *)(info->client_addr))->sin_addr.s_addr;

    char ip_str[16] = { 0 };
	snprintf(ip_str, 16, "%d.%d.%d.%d", (ip & 0xFF), (ip & 0xFF00) >> 8, (ip & 0xFF0000) >> 16, (ip & 0xFF000000) >> 24);

    lua_newtable(lua);
	lua_pushstring(lua, method.c_str());
	lua_setfield(lua, -2, "method");
	lua_pushstring(lua, url.c_str());
	lua_setfield(lua, -2, "url");
	lua_pushstring(lua, ip_str);
	lua_setfield(lua, -2, "remote");
	lua_newtable(lua);
	MHD_get_connection_values(conn, MHD_GET_ARGUMENT_KIND, &QueryIterator, lua);
	lua_setfield(lua, -2, "get");
	lua_newtable(lua);
	MHD_get_connection_values(conn, MHD_COOKIE_KIND, &QueryIterator, lua);
	lua_setfield(lua, -2, "cookie");

	lua_newtable(lua);
    if (post_data) {
        for (auto & kv : post_data->params) {
            lua_pushstring(lua, kv.second.c_str());
            lua_setfield(lua, -2, kv.first.c_str());
        }

        for (auto & kv : post_data->arrays) {
            lua_newtable(lua);
            int size = kv.second.size();
            for (int i = 0; i < size; ++i) {
                lua_pushstring(lua, kv.second[i].c_str());
                lua_rawseti(lua, -2, i + 1);
            }
            lua_setfield(lua, -2, kv.first.c_str());
        }
    }
    lua_setfield(lua, -2, "post");

    lua_newtable(lua);
    if (post_data) {
        for (auto & kv : post_data->uploaded) {
            lua_pushstring(lua, kv.second.c_str());
            lua_setfield(lua, -2, kv.first.c_str());
        }
    }
    lua_setfield(lua, -2, "file");
    return true;
}